using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Supertris
{
    class Board
    {
        public static readonly int BLOCK_SIZE = 16;

        public enum State
        {
            PieceFalling,
            LineClearing
        }

        int width, height;
        Block[,] blocks;
        public Vector2 drawPos;
        public Texture2D bgTexture;
        Rectangle bgRect;
        State state;
        List<Block> clearing = new List<Block>();
        List<int> clearingLine = new List<int>();
        bool dropping;

        public Keys LeftKey = Keys.Left;
        public Keys RightKey = Keys.Right;
        public Keys CWKey = Keys.S;
        public Keys CCWKey = Keys.A;
        public Keys DropKey = Keys.Down;

        int framesPerFall = 25;
        int framesSinceFall;
        int framesPerMove = 2;
        int framesSinceMove;

        int lockGrace = 2;  // the grace period before locking a piece in place, the number of times it checks if it's blocked below
        int lockTries;

        Texture2D blockTexture;

        Random random;

        Queue<Piece> nextPieces = new Queue<Piece>();
        int numNextPieces = 5;
        Point piecePos;

        public Board(int initWidth, int initHeight, Vector2 initDrawPos, Texture2D initBlockTexture, Texture2D initBgTexture, int randomSeed)
        {
            width = initWidth;
            height = initHeight;
            drawPos = initDrawPos;
            bgTexture = initBgTexture;
            blockTexture = initBlockTexture;

            blocks = new Block[width, height];

            bgRect = new Rectangle((int)drawPos.X, (int)drawPos.Y, width * BLOCK_SIZE, height * BLOCK_SIZE);

            piecePos = new Point(width / 2, 1);
            random = new Random(randomSeed);
            while (nextPieces.Count < numNextPieces)
            {
                PieceEnqueue();
            }
        }

        public void Update()
        {
            switch (state)
            {
                case State.PieceFalling:
                    if (Input.KeyHit(DropKey))
                        dropping = true;
                    if (dropping && Input.keyboard.IsKeyUp(DropKey))
                        dropping = false;
                    if (framesSinceFall >= framesPerFall || dropping)
                    {
                        PieceMove(new Point(0, 1));
                        framesSinceFall = 0;
                    }
                    else
                        framesSinceFall++;

                    if (Input.KeyHeld(LeftKey) && framesSinceMove >= framesPerMove)
                    {
                        PieceMove(new Point(-1, 0));
                        framesSinceMove = 0;
                    }
                    else if (Input.KeyHeld(RightKey) && framesSinceMove >= framesPerMove)
                    {
                        PieceMove(new Point(1, 0));
                        framesSinceMove = 0;
                    }
                    else
                    {
                        framesSinceMove++;
                    }
                    if (Input.KeyHit(CWKey))
                        PieceRotate(Piece.Rot.CW);
                    else if (Input.KeyHit(CCWKey))
                        PieceRotate(Piece.Rot.CCW);
                    break;

                case State.LineClearing:
                    if (clearing.Count > 0)
                    {
                        for (int i = 0; i < clearing.Count; i++)
                        {
                            while (i < clearing.Count && clearing[i].cleared)
                                clearing.RemoveAt(i);

                            if (i < clearing.Count)
                                clearing[i].ClearUpdate();
                        }
                    }
                    else
                    {
                        ClearLine(clearingLine[0], 1);
                        clearingLine.RemoveAt(0);
                        if (clearingLine.Count == 0)
                            state = State.PieceFalling;
                    }
                    break;
            }
        }

        void PieceEnqueue()
        {
            int randomPiece = /*(int)Piece.PieceType.I;*/random.Next((int)Piece.PieceType.NumTypes);
            nextPieces.Enqueue(new Piece(new Block(blockTexture, Piece.PIECE_COLORS[randomPiece]), Piece.PIECES[randomPiece]));
        }

        void PieceMove(Point offset)
        {
            if (PieceClip(offset))
            {
                if (offset == new Point(0, 1))
                {
                    lockTries++;
                    if (lockTries > lockGrace)
                    {
                        PieceLock();
                        lockTries = 0;
                    }
                }
            }
            else
            {
                if (offset == new Point(0, 1))
                    lockTries = 0;

                piecePos.X += offset.X;
                piecePos.Y += offset.Y;
            }
        }

        void PieceRotate(Piece.Rot rotation)
        {
            Piece piece = nextPieces.Peek();
            Piece oldPiece = new Piece(piece);
            piece.Rotate(rotation);
            if (PieceClip(new Point()))
            {
                // we're clipping into something

                // try moving the piece a little bit
                if (!PieceClip(new Point(-1, 0)))
                {
                    piecePos.X--;
                    return;
                }
                if (!PieceClip(new Point(1, 0)))
                {
                    piecePos.X++;
                    return;
                }
                if (!PieceClip(new Point(-2, 0)))
                {
                    piecePos.X -= 2;
                    return;
                }
                if (!PieceClip(new Point(2, 0)))
                {
                    piecePos.X += 2;
                    return;
                }

                piece.Copy(oldPiece); // revert it to the way it was
            }
        }

        bool PieceClip(Point offset)
        {
            Piece piece = nextPieces.Peek();
            for (int i = 0; i < piece.blocks.Length; i++)
            {
                Point pos = new Point(piece.blockPositions[i].X + piecePos.X + offset.X, piece.blockPositions[i].Y + piecePos.Y + offset.Y);
                if (pos.X < 0 || pos.X >= width || pos.Y < 0 || pos.Y >= height)
                    return true;
                if (blocks[pos.X, pos.Y] != null)
                    return true;
            }
            return false;
        }

        bool PieceClipBounds(Point offset)
        {
            Piece piece = nextPieces.Peek();
            for (int i = 0; i < piece.blocks.Length; i++)
            {
                Point pos = new Point(piece.blockPositions[i].X + piecePos.X + offset.X, piece.blockPositions[i].Y + piecePos.Y + offset.Y);
                if (pos.X < 0 || pos.X >= width || pos.Y < 0 || pos.Y >= height)
                    return true;
            }
            return false;
        }

        void PieceLock()
        {
            Piece piece = nextPieces.Dequeue();
            // copy the blocks from the piece onto the board
            for (int i = 0; i < piece.blocks.Length; i++)
            {
                blocks[piece.blockPositions[i].X + piecePos.X, piece.blockPositions[i].Y + piecePos.Y] = piece.blocks[i];
            }
            CheckLines();
            PieceEnqueue();
            dropping = false;
            piecePos = new Point(width / 2, 1);
            while (PieceClipBounds(new Point()))
                piecePos.Y++;
            if (PieceClip(new Point()))
            {
                // TODO: gameover
            }
        }

        void CheckLines()
        {
            int delay = 0;
            for (int row = 0; row < height; row++)
            {
                for (int col = 0; col < width; col++)
                {
                    if (blocks[col, row] == null)
                    {
                        break;
                    }
                    if (col == width - 1)
                    {
                        // this row needs to be cleared
                        StartClearLine(row, 1, delay);
                        delay += 3;
                    }
                }
            }
        }

        // row is the bottom most row, numLines is the number of lines above it to clear as well
        void StartClearLine(int row, int numLines, int delay)
        {
            state = State.LineClearing;

            for (int y = row; row - y < numLines; y--)
            {
                clearingLine.Add(y);
                for (int x = 0; x < width; x++)
                {
                    clearing.Add(blocks[x, y]);
                    blocks[x, y].ClearBegin(delay++);
                }
            }
        }

        void ClearLine(int row, int numLines)
        {
            for (int y = row; y > 0; y--)
            {
                for (int x = 0; x < width; x++)
                {
                    if (y >= numLines)
                        blocks[x, y] = blocks[x, y - numLines];
                    else
                        blocks[x, y] = null;
                }
            }
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(bgTexture, bgRect, Color.White);

            int x, y;

            for (y = 0; y < height; y++)
            {
                for (x = 0; x < width; x++)
                {
                    if (blocks[x, y] != null)
                        spriteBatch.Draw(blocks[x, y].texture, new Vector2(x * BLOCK_SIZE, y * BLOCK_SIZE) + drawPos, blocks[x, y].color);
                }
            }

            if (state == State.PieceFalling)
            {
                Piece nextPiece = nextPieces.Peek();
                for (int i = 0; i < nextPiece.blocks.Length; i++)
                {
                    spriteBatch.Draw(nextPiece.blocks[i].texture, (new Vector2(piecePos.X + nextPiece.blockPositions[i].X, piecePos.Y + nextPiece.blockPositions[i].Y) * new Vector2(BLOCK_SIZE, BLOCK_SIZE)) + drawPos, nextPiece.blocks[i].color);
                }
            }
        }
    }
}
